/*
Copyright 2021 The Karmada Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package request

import (
	"fmt"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/uuid"

	configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
	"github.com/karmada-io/karmada/pkg/util/interpreter/validation"
)

// CreateResourceInterpreterContext returns the unique request uid, the ResourceInterpreterContext object to send the webhook,
// or an error if the webhook does not support receiving any of the versions we know to send.
func CreateResourceInterpreterContext(versions []string, attributes *Attributes) (uid types.UID, request runtime.Object, err error) {
	for _, version := range versions {
		switch version {
		case configv1alpha1.GroupVersion.Version:
			uid = uuid.NewUUID()
			request = CreateV1alpha1ResourceInterpreterContext(uid, attributes)
			return
		}
	}

	err = fmt.Errorf("webhook does not accept known ResourceInterpreterContext versions (v1alpha1)")
	return
}

// CreateV1alpha1ResourceInterpreterContext creates an ResourceInterpreterContext for the provided RequestAttributes.
func CreateV1alpha1ResourceInterpreterContext(uid types.UID, attributes *Attributes) *configv1alpha1.ResourceInterpreterContext {
	r := &configv1alpha1.ResourceInterpreterContext{
		Request: &configv1alpha1.ResourceInterpreterRequest{
			UID: uid,
			Kind: metav1.GroupVersionKind{
				Group:   attributes.Object.GroupVersionKind().Group,
				Version: attributes.Object.GroupVersionKind().Version,
				Kind:    attributes.Object.GetKind(),
			},
			Name:      attributes.Object.GetName(),
			Namespace: attributes.Object.GetNamespace(),
			Operation: attributes.Operation,
			Object: runtime.RawExtension{
				Object: attributes.Object.DeepCopyObject(),
			},
			DesiredReplicas:  &attributes.ReplicasSet,
			AggregatedStatus: attributes.AggregatedStatus,
		},
	}

	if attributes.ObservedObj != nil {
		r.Request.ObservedObject = &runtime.RawExtension{Object: attributes.ObservedObj.DeepCopyObject()}
	}
	return r
}

// VerifyResourceInterpreterContext checks the validity of the provided resourceInterpreterContext, and returns ResponseAttributes,
// or an error if the provided resourceInterpreterContext was not valid.
func VerifyResourceInterpreterContext(uid types.UID, operation configv1alpha1.InterpreterOperation, interpreterContext runtime.Object) (response *ResponseAttributes, err error) {
	switch r := interpreterContext.(type) {
	case *configv1alpha1.ResourceInterpreterContext:
		if r.Response == nil {
			return nil, fmt.Errorf("webhook response was absent")
		}

		if r.Response.UID != uid {
			return nil, fmt.Errorf("expected response.uid %q, got %q", uid, r.Response.UID)
		}

		res, err := verifyResourceInterpreterContext(operation, r.Response)
		if err != nil {
			return nil, err
		}
		return res, nil
	default:
		return nil, fmt.Errorf("unexpected response type %T", interpreterContext)
	}
}

func verifyResourceInterpreterContext(operation configv1alpha1.InterpreterOperation, response *configv1alpha1.ResourceInterpreterResponse) (*ResponseAttributes, error) {
	res := &ResponseAttributes{}

	res.Successful = response.Successful
	if !response.Successful {
		if response.Status == nil {
			return nil, fmt.Errorf("webhook require response.status when response.successful is false")
		}
		res.Status = *response.Status
		return res, nil
	}

	switch operation {
	case configv1alpha1.InterpreterOperationInterpretReplica:
		if response.Replicas == nil {
			return nil, fmt.Errorf("webhook returned nil response.replicas")
		}
		res.Replicas = *response.Replicas
		res.ReplicaRequirements = response.ReplicaRequirements
		return res, nil
	case configv1alpha1.InterpreterOperationInterpretComponent:
		res.Components = response.Components
		return res, nil
	case configv1alpha1.InterpreterOperationInterpretDependency:
		err := validation.VerifyDependencies(response.Dependencies)
		if err != nil {
			return nil, err
		}
		res.Dependencies = response.Dependencies
		return res, nil
	case configv1alpha1.InterpreterOperationPrune, configv1alpha1.InterpreterOperationReviseReplica,
		configv1alpha1.InterpreterOperationRetain, configv1alpha1.InterpreterOperationAggregateStatus:
		err := verifyResourceInterpreterContextWithPatch(response)
		if err != nil {
			return nil, err
		}
		res.Patch = response.Patch
		if response.PatchType != nil {
			res.PatchType = *response.PatchType
		}
		return res, nil
	case configv1alpha1.InterpreterOperationInterpretStatus:
		if response.RawStatus == nil {
			return nil, fmt.Errorf("webhook returned nill response.rawStatus")
		}
		res.RawStatus = *response.RawStatus
		return res, nil
	case configv1alpha1.InterpreterOperationInterpretHealth:
		if response.Healthy == nil {
			return nil, fmt.Errorf("webhook returned nil response.healthy")
		}
		res.Healthy = *response.Healthy
		return res, nil
	default:
		return nil, fmt.Errorf("input wrong operation type: %s", operation)
	}
}

func verifyResourceInterpreterContextWithPatch(response *configv1alpha1.ResourceInterpreterResponse) error {
	if len(response.Patch) == 0 && response.PatchType == nil {
		return nil
	}
	if response.PatchType == nil {
		return fmt.Errorf("webhook returned nil response.patchType")
	}
	if len(response.Patch) == 0 {
		return fmt.Errorf("webhook returned empty response.patch")
	}
	patchType := *response.PatchType
	if patchType != configv1alpha1.PatchTypeJSONPatch {
		return fmt.Errorf("webhook returned invalid response.patchType of %q", patchType)
	}
	return nil
}
